/*
 * MIT License
 *
 * Copyright (c) 2020 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.postman.request.analyzer;

import cn.seaboot.common.core.CommonUtils;
import cn.seaboot.common.reflect.ObjectUtils;
import cn.seaboot.postman.PostmanConfiguration;
import cn.seaboot.postman.analyzer.MethodInfo;
import cn.seaboot.postman.request.Query;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;
import org.hibernate.validator.constraints.Length;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * {@link Query}
 * One request contain request-parameter, header, url, etc.
 * This interface use for finding all request-parameter from code.
 *
 * @author Mr.css
 * @date 2021-01-31 14:29
 */
public class DefaultQueryAnalyzer implements QueryAnalyzer {

    /**
     * {@inheritDoc}
     */
    @Override
    @org.jetbrains.annotations.NotNull
    public List<Query> analyzeQuery(PostmanConfiguration configuration, MethodInfo method) {
        return this.getAllQuery(method);
    }

    /**
     * It will search all Query form method-parameter, value-object and annotation;
     * If get multiple Query with the same name, will merge all of them, and use first one.
     *
     * @param method method
     * @return all request-parameter of method
     */
    private List<Query> getAllQuery(MethodInfo method) {
        List<Query> queryList = new ArrayList<>();
        for (Parameter parameter : method.getParameters()) {
            Class parameterType = parameter.getType();
            if (ObjectUtils.isJavaBean(parameterType)) {
                //如果对象不存在于java和javax包，解析对象的字段
                getQueryFormVo(parameter, parameterType, queryList);
            } else if (ObjectUtils.isBaseField(parameterType)) {
                //java、javax 包只解析基础类型
                String key = parameter.getName();
                Query query = new Query();
                query.setKey(key);
                mergeQuery(queryList, query);
            }
        }
        //@Operation 注解类型
        getQueryFormOperation(method, queryList);
        return queryList;
    }

    /**
     * In this case: Vo is a parameter of Method, get all Query form Vo.
     *
     * @param parameter method-parameter
     * @param voClass   voType
     * @param queryList queryList
     */
    private void getQueryFormVo(Parameter parameter, Class voClass, List<Query> queryList) {
        Validated validated = parameter.getAnnotation(Validated.class);
        Class<?>[] parameterGroup = null;
        if (validated != null) {
            parameterGroup = validated.value();
        }

        Field[] fields = voClass.getDeclaredFields();
        for (Field field : fields) {
            if (!isHideField(field)) {
                Class fieldType = field.getType();
                if (ObjectUtils.isBaseField(fieldType)) {
                    Query query = new Query();
                    query.setKey(field.getName());
                    String description = getFullDescriptionFormVoField(parameterGroup, field);
                    query.setDescription(description);
                    mergeQuery(queryList, query);
                }
            }
        }
    }

    /**
     * 判断是不是隐藏字段
     * check if ignore Field of VO or not
     *
     * @param field filed Form VO
     * @return boolean
     */
    private boolean isHideField(Field field) {
        Schema schema = field.getAnnotation(Schema.class);
        if (schema != null) {
            return schema.hidden();
        } else {
            return false;
        }
    }


    /**
     * get description form Field
     *
     * @param field VO field
     * @return description
     */
    private String getDescriptionFormVoField(Field field) {
        Schema schema = field.getAnnotation(Schema.class);
        if (schema != null) {
            return schema.description();
        } else {
            return null;
        }
    }


    /**
     * 获取一个Method所包含的全部参数信息
     * <p>
     * In this case: Method is marked by @Operation, get all Query form @Operation
     *
     * @param method    Controller-Method
     * @param queryList queryList
     */
    private void getQueryFormOperation(MethodInfo method, List<Query> queryList) {
        io.swagger.v3.oas.annotations.Parameter[] params = null;
        //扫描Parameters中的Parameter注解
        Parameters parameters = method.getMethod().getDeclaredAnnotation(Parameters.class);
        if (parameters != null) {
            params = parameters.value();
        }

        //扫描Operation中的Parameter注解
        if (CommonUtils.isNotEmpty(params)) {
            Operation operation = method.getMethod().getDeclaredAnnotation(Operation.class);
            if (operation != null) {
                params = operation.parameters();
            }
        }
        if (CommonUtils.isNotEmpty(params)) {
            for (io.swagger.v3.oas.annotations.Parameter param : params) {
                if (!param.hidden()) {
                    Query query = new Query();
                    query.setKey(param.name());
                    query.setValue(param.example());
                    query.setDescription(param.description());
                    mergeQuery(queryList, query);
                }
            }
        }
    }

    /**
     * 如果出现重复的参数注解，合并全部的参数描述
     * <p>
     * If get multiple Query with the same name, will merge all of them
     *
     * @param queryList last return value
     * @param query     request-parameter
     */
    private void mergeQuery(List<Query> queryList, Query query) {
        for (Query q : queryList) {
            if (q.getKey().equals(query.getKey())) {
                String s1 = q.getDescription();
                String s2 = query.getDescription();
                if (CommonUtils.isEmpty(s1)) {
                    //不存在描述，使用最新扫描到的数据
                    q.setDescription(s2);
                } else if (CommonUtils.isNotEmpty(s2)) {
                    //如果存在描述，并且扫描到新的描述，则合并全部
                    if (!s1.contains(s2)) {
                        q.setDescription(q.getDescription() + "|" + s2);
                    }
                }

                if (CommonUtils.isNotEmpty(query.getValue())) {
                    q.setValue(query.getValue());
                }
                return;
            }
        }
        queryList.add(query);
    }


    //hibernate-validator start------------------------------------------------------


    /**
     * concat all description from field.
     *
     * @param parameterGroup parameter validate-group
     * @param field          VO field
     * @return description E.G.: '应用名称|Length[0,64]|notNull'
     */
    private String getFullDescriptionFormVoField(Class<?>[] parameterGroup, Field field) {
        StringBuilder sb = new StringBuilder();
        String str = getDescriptionFormVoField(field);
        if (str != null) {
            sb.append(str).append("|");
        }
        if ((str = getNullable(parameterGroup, field)) != null) {
            sb.append(str).append("|");
        }
        if ((str = getNotEmpty(parameterGroup, field)) != null) {
            sb.append(str).append("|");
        }
        if ((str = getLength(parameterGroup, field)) != null) {
            sb.append(str).append("|");
        }
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
        }
        return sb.toString();
    }

    /**
     * hibernate-validator configuration.
     * Compare validate group, if in the same group, will get description form hibernate-validator.
     *
     * @param methodGroup methodGroup
     * @param fieldGroup  fieldGroup
     * @return result
     */
    private boolean isAcceptValidator(Class<?>[] methodGroup, Class<?>[] fieldGroup) {
        if (CommonUtils.isEmpty(methodGroup) && CommonUtils.isEmpty(fieldGroup)) {
            //都不指定分组，Validator生效
            return true;
        } else {
            //RequestParam或者Field，只要其中一个指定了分组，则必须匹配后才能生效
            for (Class<?> g1 : methodGroup) {
                for (Class<?> g2 : fieldGroup) {
                    if (g1.equals(g2)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    /**
     * hibernate-validator configuration.
     * get validate description form @NotNull
     *
     * @param parameterGroup parameter validate-group
     * @param field          VO field
     * @return description
     */
    private String getNullable(Class<?>[] parameterGroup, Field field) {
        NotNull notNull = field.getAnnotation(NotNull.class);
        if (notNull != null && isAcceptValidator(parameterGroup, notNull.groups())) {
            return "NotNull";
        }
        return null;
    }

    /**
     * hibernate-validator configuration.
     * get validate description form @NotEmpty
     *
     * @param parameterGroup parameter validate-group
     * @param field          VO field
     * @return description
     */
    private String getNotEmpty(Class<?>[] parameterGroup, Field field) {
        NotEmpty notNull = field.getAnnotation(NotEmpty.class);
        if (notNull != null && isAcceptValidator(parameterGroup, notNull.groups())) {
            return "NotEmpty";
        }
        return null;
    }

    /**
     * hibernate-validator configuration.
     * get validate description form @Length
     *
     * @param parameterGroup parameter validate-group
     * @param field          VO field
     * @return description
     */
    private String getLength(Class<?>[] parameterGroup, Field field) {
        Length length = field.getAnnotation(Length.class);
        if (length != null && isAcceptValidator(parameterGroup, length.groups())) {
            return "Length[" + length.min() + "," + length.max() + "]";
        }
        return null;
    }

    //hibernate-validator end------------------------------------------------------
}
